#!/usr/bin/env python

"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
For a copy of the license see .
=====================================================================
www.peileppe.com

squadron4g - graphic version using pygcurse

https://github.com/asweigart/pygcurse
http://inventwithpython.com/pygcurse
Pygcurse requires Pygame to be installed. Pygame can be downloaded from http://pygame.org

"""

import pygcurse, pygame, sys, time
from random import randint

AREA_WIDTH=40
AREA_HEIGHT=40

# colors palette
BLACK = (0,0,0)
BACKG = (0xe7, 0xf1, 0xf2)
AVATR = (0xef, 0x94, 0x34)
BLOCK = (0x95, 0xc4, 0xca)
CHASR = (0xfe, 0xd9,0x30)
BALLC = (0x57, 0x57, 0x6d)
FLAGC = (0xba, 0xef, 0xaa) 
FLAGD = BLOCK # decoration around the flag

FPS=15

KEY_UP= 273
KEY_DOWN=274
KEY_RIGHT=275
KEY_LEFT=276

def initscr():
    win = pygcurse.PygcurseWindow(AREA_WIDTH, AREA_HEIGHT, fullscreen=False)
    pygame.display.set_caption('Pygcurse Squadron4g')
    win.autowindowupdate = False
    win.autoupdate = False
    win.fill(bgcolor=BACKG)
    return win
    
def addstr(win, y, x, msg):
    win.cursor = (x, y)
    win.write(msg, fgcolor=BACKG, bgcolor=BALLC) # or may use win.putchars - what's the difference?
    return

def addch(win, y, x, color, symbol=None):
    if symbol!=None:
        win.putchar(symbol, x, y, fgcolor=BLACK, bgcolor=color)
    else:
        win.putchar('.', x, y, fgcolor=BLACK, bgcolor=color) # decor 
    return 

def inch(w, y, x ):
        # The self._screen* members are 2D lists that store data for each cell of the PygcurseSurface object.
        #_screenchar[x][y] holds the character at cell x, y. _screenfgcolor and _screenbgcolor  stores the 
        # foreground/background color of the cell, etc.
        #self._screenchar = [[None] * height for i in range(width)]
    return ord(w._screenchar[x][y])
    
def refresh(win):
    global mainClock
    win.update()
    pygame.display.update()    
    mainClock.tick(FPS)    
    return

def highscore(w, score, action ):
    if action==ord('q'):
        return
    if score>0:
        msg=" >High Score : "+str(score)
    else:
        msg=" > Failed!"
    addstr(w, 20,15,msg)
    refresh(w)
    time.sleep(5)
    return

def printbox(x, y, x1, y1, ws):
# to draw platforms 
    for py in range(y, y1):
        for px in range(x, x1):
            addch(ws, py, px, BLOCK)
    return

def setDecor(w, xx, yy):
    # generating platforms / obstacles
    for x in range(0,AREA_WIDTH-1):
        addch(w, 0, x, BLOCK)
        addch(w, AREA_HEIGHT-1, x, BLOCK)
    for y in range(0,AREA_HEIGHT-1):       
        addch(w, y, 0,  BLOCK)
        addch(w, y, AREA_WIDTH-1,  BLOCK)
    printbox(18,3,20,4,w)
    printbox(7,10,9,11,w)
    printbox(30,10,32,11,w)
    printbox(18,13,20,14,w)
    printbox(4,18,5,20,w)
    printbox(16,16,17,22,w)
    printbox(21,16,22,22,w)
    printbox(35,18,36,20,w)
    printbox(18,24,20,25,w)
    printbox(7,28,9,29,w)
    printbox(30,28,32,29,w)
    printbox(18,35,20,36,w)
    return
    
def screenUpdate(w, score):
    msg=".["+str(score)+"]."
    addstr(w,0,1,msg)
    return
    
class movingObject(object):    
    def __init__(self, x, y, symbol):
        self.x, self.y, self.symbol=x, y, symbol
        self.alive=True
        self.color=CHASR
        return
        
    def showandClean(self, w, newposx, newposy):
        if self.alive==False:        
            return 0, 0
        addch(w, self.y,self.x,BACKG, ' ') # erase old emplacement
        addch(w, newposy,newposx,self.color, self.symbol)
        return newposx, newposy
#==> end class movingObject

class ball(movingObject):
    def __init__(self, x, y, symbol):
        movingObject.__init__(self, x, y, symbol)
        self.dir_x, self.dir_y=1, 1
        self.color=BACKG
        return
        
    def interpretBounce(self, w):
        if self.alive==False:
            return 0,0
        new_x, new_y = self.x+self.dir_x, self.y+self.dir_y
        sane = 0 
        while (inch(w, new_y,new_x) != ord(' ') or \
        new_y not in range(1,AREA_HEIGHT-1) or \
        new_x not in range(1,AREA_WIDTH-1)) and sane < 4 :
            if sane % 2 != 0:
                self.dir_x = -self.dir_x
            else:
                self.dir_y = -self.dir_y
            new_x = self.x+self.dir_x
            new_y = self.y+self.dir_y
            sane += 1
        if sane==4: # move is not possible
            self.alive=False
            new_x, new_y=self.x, self.y 
            addch(w,self.y,self.x, BLOCK, '=') # a dead cell is kept
        return new_x, new_y
#==> end class ball

class magnetBlock(movingObject):
    def __init__(self, x, y, symbol, dir_x, dir_y):
        movingObject.__init__(self, x, y, symbol)
        self.dir_x, self.dir_y= dir_x, dir_y
        self.color=BLOCK
        return

    def interpretAim(self, w):
        if self.alive==False:
            return 0,0
        new_x, new_y = self.x+self.dir_x, self.y+self.dir_y
        if self.dir_y==0:
            self.symbol='~'
        else:
            self.symbol=':'
        if inch(w, new_y,new_x) != ord(' '):
            self.alive=False
            new_x, new_y=self.x, self.y 
            addch(w, self.y,self.x,BLOCK, 'B') # blockMagnet is shown
        return new_x, new_y
#==> end class magnetBlock

class mePlayer(movingObject):
    def __init__(self, x, y, symbol):
        movingObject.__init__(self, x, y, symbol)
        self.dir_x, self.dir_y = 0, 0 # no movement
        self.magnetBlocks=[]
        self.win=0
        self.color=AVATR
        return

    def interpretKey(self, w, xx, yy, action, ax, ay, score):
        validspot=(ord(' '), ord('1'), ord('2'), ord('3'), ord('4'))
        if action == KEY_UP and ay > 1 \
        and inch(w, ay-1, ax) in validspot:
            self.dir_y=-1 ; self.dir_x=0 ; ay -=1
        elif action == KEY_DOWN and ay < yy-2 \
        and inch(w, ay+1,ax) in validspot:
            self.dir_y=+1 ; self.dir_x=0; ay +=1
        elif action == KEY_RIGHT and ax < xx-2 \
        and inch(w, ay,ax+1) in validspot:
            self.dir_x=+1 ; self.dir_y=0; ax  +=1
        elif action == KEY_LEFT and ax > 1 \
        and inch(w, ay, ax-1) in validspot:
            self.dir_x=-1 ; self.dir_y=0; ax -=1
        elif action == ord(' ') and score > 20: # press fire
            self.magnetBlocks.append(magnetBlock(ax,ay,"~", self.dir_x, self.dir_y ))
            score-=20 # cost to use magnetBlock
        elif action == ord('t') and score >100 : # teleport
            self.dir_y=0 ; self.dir_x = 0 ; ax = xx/2-1 ; ay= yy/2-1
            score-=100 # cost to use Teleport

        final_spot=inch(w, ay, ax) # check if flag are captured
        if  final_spot == ord('1'): 
            addch(w, 39, 25, FLAGC, '1')
            self.win+=1
        elif final_spot == ord('2'):
            addch(w, 39, 29, FLAGC, '2')
            self.win+=1
        elif final_spot == ord('3'):
            addch(w, 39, 33, FLAGC, '3')
            self.win+=1
        elif final_spot == ord('4'):
            addch(w, 39, 37, FLAGC, '4')   
            self.win+=1

        if self.win==4: # if all 4 flags are captured
            output=open("squadron4-highscore.txt","a")
            output.write("High Score = ["+str(score)+"]\n")
            output.close()
            return ax, ay, score
            
        for m in self.magnetBlocks: # managing all magnetBlocks in transit
            new_x, new_y = m.interpretAim(w)
            m.x, m.y = m.showandClean(w, new_x, new_y)
            
        return  ax, ay, score
#==> end class mePlayer
    
class chaser(movingObject):
    def interpretChase(self, w, me):
        dx=me.x-self.x
        dy=me.y-self.y
        ch_x, ch_y =self.x, self.y
        if abs(dx) > abs(dy):
            if dx>0 and inch(w, ch_y, ch_x+1) == ord(' ') :
                ch_x +=1
            elif dx<0 and inch(w, ch_y,ch_x-1) == ord(' ') :
                ch_x -=1        
        else:
            if dy>0 and inch(w, ch_y+1, ch_x) == ord(' ') :
                ch_y +=1
            elif dy<0 and inch(w, ch_y-1, ch_x) == ord(' ') :
                ch_y -=1
        return ch_x, ch_y
#==> end class chaser
    
def main():
    global mainClock
    w=initscr()
    mainClock = pygame.time.Clock()
    yy, xx = AREA_HEIGHT, AREA_WIDTH
    setDecor(w,xx,yy)
    balls=[]
    balls.append(ball(2,10,'O'))
    balls.append(ball(19,10,'O'))
    balls.append(ball(37,10,'O'))
    balls.append(ball(2,28,'O'))
    balls.append(ball(19,28,'O'))
    balls.append(ball(37,28,'O'))
    chasers=[]
    chasers.append(chaser(4,5, 'C'))
    chasers.append(chaser(4,34, 'D'))
    chasers.append(chaser(36,4, 'E'))
    chasers.append(chaser(35,5, 'F'))
    # declaring the cursor controlled by player
    me  = mePlayer(xx/2-1,yy/2-1,'@')  
    # preparing the 4 flags in the area and receptacles on status line
    addch(w, 3,3,  FLAGD, '[') ;     addch(w, 3, 4, FLAGC,'1');     addch(w, 3, 5, FLAGD,']')
    addch(w, 3, 34, FLAGD,'[');     addch(w, 3, 35, FLAGC,'2');     addch(w, 3, 36, FLAGD,']')
    addch(w, 36, 3,  FLAGD,'[');     addch(w, 36, 4, FLAGC,'3');     addch(w, 36, 5, FLAGD,']')
    addch(w, 36, 34, FLAGD,'[');     addch(w, 36, 35, FLAGC,'4');     addch(w,  36, 36, FLAGD, ']')
    addstr(w, 39,24, '[ ].[ ].[ ].[ ]') 
    scoreMagnet = 1000 # countdown initialized
    action= None
    while action!=ord('q') and me.win<4 and scoreMagnet>0:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                action = event.key
            elif event.type == pygame.KEYUP:
                action = None
        me_x, me_y, scoreMagnet=me.interpretKey(w, xx, yy, action, me.x, me.y, scoreMagnet)
        me.x, me.y=me.showandClean(w, me_x, me_y)        
        for b in balls:
            new_x, new_y = b.interpretBounce(w)
            b.x, b.y = b.showandClean(w, new_x, new_y)
        for c in chasers:
            ch_x, ch_y = c.interpretChase(w, me)
            c.x, c.y= c.showandClean(w, ch_x, ch_y)
        screenUpdate(w, scoreMagnet)
        scoreMagnet -= 1
        refresh(w)
    highscore(w,scoreMagnet, action)

if __name__ == '__main__':
    main()